package com.hf.log4j;

import java.util.List;
import java.util.Properties;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.HTMLLayout;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.helpers.CyclicBuffer;
import org.apache.log4j.helpers.LogLog;
import org.apache.log4j.helpers.OptionConverter;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.TriggeringEventEvaluator;
import org.apache.log4j.xml.UnrecognizedElementHandler;
import org.w3c.dom.Element;

import com.hf.application.ApplicationContextHolder;
import com.hf.mail.MailMessage;
import com.hf.mail.MailSenderService;

public class MailAppender extends AppenderSkeleton implements
		UnrecognizedElementHandler {
	private List<String> to;
	private List<String> cc;
	private List<String> bcc;

	private String from;
	private String fromPersonal;

	private String subject;

	private int bufferSize = 512;

	/**
	 * The <b>LocationInfo</b> option takes a boolean value. By default, it is
	 * set to false which means there will be no effort to extract the location
	 * information related to the event. As a result, the layout that formats
	 * the events as they are sent out in an e-mail is likely to place the wrong
	 * location information (if present in the format).
	 * 
	 * <p>
	 * Location information extraction is comparatively very slow and should be
	 * avoided unless performance is not a concern.
	 */
	private boolean locationInfo = false;
	private boolean sendOnClose = false;

	protected CyclicBuffer cb = new CyclicBuffer(bufferSize);

	protected TriggeringEventEvaluator evaluator;

	private MailSenderService mailSenderService;

	private MailSenderService getMailSenderService() {
		if (mailSenderService == null) {
			mailSenderService = (MailSenderService) ApplicationContextHolder
					.getBean("mailSenderService");
		}
		return mailSenderService;
	}

	public MailAppender() {
		this(new TriggeringEventEvaluator() {
			public boolean isTriggeringEvent(LoggingEvent event) {
				return event.getLevel().isGreaterOrEqual(Level.ERROR);
			}
		});
	}

	public MailAppender(TriggeringEventEvaluator evaluator) {
		this.evaluator = evaluator;
	}

	public void activateOptions() {
	}

	/**
	 * Perform SMTPAppender specific appending actions, mainly adding the event
	 * to a cyclic buffer and checking if the event triggers an e-mail to be
	 * sent.
	 */
	public void append(LoggingEvent event) {

		if (!checkEntryConditions()) {
			return;
		}

		event.getThreadName();
		event.getNDC();
		event.getMDCCopy();
		if (locationInfo) {
			event.getLocationInformation();
		}
		event.getRenderedMessage();
		event.getThrowableStrRep();
		cb.add(event);
		if (evaluator.isTriggeringEvent(event)) {
			sendBuffer();
		}
	}

	/**
	 * This method determines if there is a sense in attempting to append.
	 * 
	 * <p>
	 * It checks whether there is a set output target and also if there is a set
	 * layout. If these checks fail, then the boolean value <code>false</code>
	 * is returned.
	 */
	protected boolean checkEntryConditions() {
		if (this.getMailSenderService() == null) {
			errorHandler.error("Message object not configured.");
			return false;
		}

		if (this.evaluator == null) {
			errorHandler
					.error("No TriggeringEventEvaluator is set for appender ["
							+ name + "].");
			return false;
		}

		if (this.layout == null) {
			errorHandler.error("No layout set for appender named [" + name
					+ "].");
			return false;
		}
		return true;
	}

	synchronized public void close() {
		this.closed = true;
		if (sendOnClose && cb.length() > 0) {
			sendBuffer();
		}
	}

	public boolean requiresLayout() {
		return true;
	}

	/**
	 * Layout body of email message.
	 * 
	 * @since 1.2.16
	 */
	protected String formatBody() {

		// Note: this code already owns the monitor for this
		// appender. This frees us from needing to synchronize on 'cb'.

		StringBuffer sbuf = new StringBuffer();
		String t = layout.getHeader();
		if (t != null)
			sbuf.append(t);
		int len = cb.length();
		for (int i = 0; i < len; i++) {
			// sbuf.append(MimeUtility.encodeText(layout.format(cb.get())));
			LoggingEvent event = cb.get();
			sbuf.append(layout.format(event));
			if (layout.ignoresThrowable()) {
				String[] s = event.getThrowableStrRep();
				if (s != null) {
					for (int j = 0; j < s.length; j++) {
						sbuf.append(s[j]);
						sbuf.append(Layout.LINE_SEP);
					}
				}
			}
		}
		t = layout.getFooter();
		if (t != null) {
			sbuf.append(t);
		}

		return sbuf.toString();
	}

	/**
	 * Send the contents of the cyclic buffer as an e-mail message.
	 */
	protected void sendBuffer() {
		try {
			String s = formatBody();

			MailMessage mailMessage = new MailMessage();
			if (from != null) {
				mailMessage.from = from;
				mailMessage.fromPersonal = fromPersonal;
			}

			mailMessage.to = to;
			mailMessage.Cc = cc;
			mailMessage.Bcc = bcc;
			mailMessage.subject = subject;

			if (layout instanceof HTMLLayout) {
				mailMessage.htmlText = s;
			} else {
				mailMessage.plainText = s;
			}

			getMailSenderService().sendAsyn(mailMessage);
		} catch (RuntimeException e) {
			LogLog.error("Error occured while sending e-mail notification.", e);
		}
	}

	public List<String> getTo() {
		return to;
	}

	public void setTo(List<String> to) {
		this.to = to;
	}

	public List<String> getCc() {
		return cc;
	}

	public void setCc(List<String> cc) {
		this.cc = cc;
	}

	public List<String> getBcc() {
		return bcc;
	}

	public void setBcc(List<String> bcc) {
		this.bcc = bcc;
	}

	public String getFrom() {
		return from;
	}

	public void setFrom(String from) {
		this.from = from;
	}

	public String getFromPersonal() {
		return fromPersonal;
	}

	public void setFromPersonal(String fromPersonal) {
		this.fromPersonal = fromPersonal;
	}

	public String getSubject() {
		return subject;
	}

	public void setSubject(String subject) {
		this.subject = subject;
	}

	public boolean isLocationInfo() {
		return locationInfo;
	}

	public void setLocationInfo(boolean locationInfo) {
		this.locationInfo = locationInfo;
	}

	public boolean isSendOnClose() {
		return sendOnClose;
	}

	public void setSendOnClose(boolean sendOnClose) {
		this.sendOnClose = sendOnClose;
	}

	public int getBufferSize() {
		return bufferSize;
	}

	/**
	 * Returns value of the <b>EvaluatorClass</b> option.
	 */
	public String getEvaluatorClass() {
		return evaluator == null ? null : evaluator.getClass().getName();
	}

	/**
	 * The <b>BufferSize</b> option takes a positive integer representing the
	 * maximum number of logging events to collect in a cyclic buffer. When the
	 * <code>BufferSize</code> is reached, oldest events are deleted as new
	 * events are added to the buffer. By default the size of the cyclic buffer
	 * is 512 events.
	 */
	public void setBufferSize(int bufferSize) {
		this.bufferSize = bufferSize;
		cb.resize(bufferSize);
	}

	/**
	 * The <b>EvaluatorClass</b> option takes a string value representing the
	 * name of the class implementing the {@link TriggeringEventEvaluator}
	 * interface. A corresponding object will be instantiated and assigned as
	 * the triggering event evaluator for the SMTPAppender.
	 */
	public void setEvaluatorClass(String value) {
		evaluator = (TriggeringEventEvaluator) OptionConverter
				.instantiateByClassName(value, TriggeringEventEvaluator.class,
						evaluator);
	}

	/**
	 * Sets triggering evaluator.
	 * 
	 * @param trigger
	 *            triggering event evaluator.
	 * @since 1.2.15
	 */
	public final void setEvaluator(final TriggeringEventEvaluator trigger) {
		if (trigger == null) {
			throw new NullPointerException("trigger");
		}
		this.evaluator = trigger;
	}

	public boolean parseUnrecognizedElement(final Element element,
			final Properties props) throws Exception {
		if ("triggeringPolicy".equals(element.getNodeName())) {
			Object triggerPolicy = org.apache.log4j.xml.DOMConfigurator
					.parseElement(element, props,
							TriggeringEventEvaluator.class);
			if (triggerPolicy instanceof TriggeringEventEvaluator) {
				setEvaluator((TriggeringEventEvaluator) triggerPolicy);
			}
			return true;
		}

		return false;
	}

}
